package com.avengers.core.model;

import com.avengers.core.AbstractEnhancerConfig;
import com.avengers.core.bean.enums.SymbolEnum;
import com.avengers.core.kit.JsonKit;
import com.avengers.core.kit.StringKit;
import com.avengers.core.kit.TimeKit;
import com.avengers.core.model.db.*;
import com.jfinal.kit.StrKit;
import com.jfinal.plugin.activerecord.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 在主从多库对应一个model的情况有待考虑
 *
 * @author yu.wang
 * @since 2018-04-03 下午6:21
 */
@SuppressWarnings("unused")
public abstract class AbstractEnhancerModel<M extends AbstractEnhancerModel<M>> extends Model<M> implements SqlKey {
    private static final long serialVersionUID = 1L;

    private static final Logger logger = LoggerFactory.getLogger(AbstractEnhancerModel.class);

    private final transient ThreadLocal<SqlCondition> threadLocal = new ThreadLocal<>();

    public static final DbColumn NORMAL_PRIMARY_ID = () -> "id";

    public M showLog(boolean showLog) {
        this.getThreadSqlCondition().setShowLog(showLog);
        return (M) this;
    }

    public Integer getChooseYear() {
        ThreadLocal<Integer> yearThreadLocal = getChooseYearThreadLocal();
        if (null != yearThreadLocal) {
            return yearThreadLocal.get();
        }
        return null;
    }

    public void setChooseYear(Integer chooseYear) {
        ThreadLocal<Integer> yearThreadLocal = getChooseYearThreadLocal();
        if (null != yearThreadLocal && null != chooseYear) {
            yearThreadLocal.set(chooseYear);
        }
    }

    public void clearChooseYear() {
        ThreadLocal<Integer> yearThreadLocal = getChooseYearThreadLocal();
        if (null != yearThreadLocal) {
            Class<?> clazz = _getUsefulClass();
            Integer chooseYear = yearThreadLocal.get();
            yearThreadLocal.remove();
            logger.info("{}.{} choose year {} remove after =====>>>>> {}", clazz.getPackage().getName(), clazz.getSimpleName(), chooseYear, yearThreadLocal.get());
        }
    }

    protected ThreadLocal<Integer> getChooseYearThreadLocal() {
        return null;
    }

    private Table getFinalTable() {
        Integer chooseYear = getChooseYear();
        if (null != chooseYear) {
            Class<?> clazz = _getUsefulClass();
            String classFullName = String.format("%s.%s", clazz.getPackage().getName(), clazz.getSimpleName());
            String yearClassName = String.format("%s.%s%d", clazz.getPackage().getName(), clazz.getSimpleName(), chooseYear);
            logger.info("{} --> {}", classFullName, yearClassName);
            Class<? extends AbstractEnhancerModel<?>> yearClass = null;
            try {
                yearClass = (Class<? extends AbstractEnhancerModel<?>>) Class.forName(yearClassName);
            } catch (Exception ignore) {
            }
            if (null != yearClass) {
                Table table = TableMapping.me().getTable(yearClass);
                if (null != table) {
                    return table;
                }
            }
            logger.error("{} choose year {}({}) class not found", classFullName, chooseYear, yearClassName);
        }
        return super._getTable();
    }

    public String getFinalTableName() {
        Table finalTable = getFinalTable();
        return finalTable.getName();
    }

    @Override
    protected Table _getTable() {
        return getFinalTable();
    }

    /**
     * 表名
     */
    public String getTableName() {
        return getFinalTableName();
    }

    protected String getSqlTableName() {
        String tableName = getFinalTableName();
        if (StringKit.isBlank(tableName)) {
            tableName = getTableName();
        }
        if (tableName.contains(SymbolEnum.sqlQuotes.string)) {
            return tableName;
        }
        return SymbolEnum.sqlQuotes + tableName + SymbolEnum.sqlQuotes;
    }

    /**
     * 数据库名
     */
    protected abstract String getConfigName();

    protected boolean containsColumn(String name) {
        return Boolean.FALSE;
    }

    public List<Object> getParamList() {
        return getThreadSqlCondition().getParamList();
    }

    public String toFullSql(DbColumn... columns) {
        return toFullSql(Boolean.FALSE, columns);
    }

    public synchronized String toFullSql(boolean clear, DbColumn... columns) {
        String condition = getThreadSqlCondition().toSql();
        StringBuffer selectSql = new StringBuffer();
        selectSql.append(genSelectSql(columns));

        appendFromConditionSql(selectSql, condition);
        try {
            return selectSql.toString();
        } finally {
            if (clear) {
                getThreadSqlCondition().clearCondition();
                threadLocal.remove();
            }
        }
    }

    public synchronized String toConditionSql(boolean clear) {
        try {
            return getThreadSqlCondition().toSql();
        } finally {
            if (clear) {
                getThreadSqlCondition().clearCondition();
            }
        }
    }

    public String toConditionSql() {
        return toConditionSql(Boolean.FALSE);
    }

    private M condition(String link, M sqlCondition, boolean isNot) {
        getThreadSqlCondition().getConditionSql().append(link).append(SymbolEnum.space).append(isNot ? SymbolEnum.not : SymbolEnum.empty).append(BRACKETS_LEFT).append(sqlCondition.toConditionSql()).append(BRACKETS_RIGHT);
        getThreadSqlCondition().getParamList().addAll(sqlCondition.getParamList());
        return (M) this;
    }

    private M equals(String link, boolean whether, DbColumn column, Object oValue) {
        if (null == column || null == oValue) {
            return (M) this;
        }
        if (oValue instanceof DbColumn) {
            getThreadSqlCondition().getConditionSql().append(link).append(column.sqlColumn()).append(whether ? EQUAL : NOT_EQUAL).append(((DbColumn) oValue).name());
            return (M) this;
        }
        getThreadSqlCondition().getConditionSql().append(link).append(column.sqlColumn()).append(whether ? EQUAL : NOT_EQUAL).append(PARAM);
        getThreadSqlCondition().getParamList().add(oValue);
        return (M) this;
    }

    private M in(String link, boolean whether, DbColumn column, Object oValues) {
        if (null == column || null == oValues) {
            return (M) this;
        }
        if (oValues.getClass().isArray()) {
            int valueLength = Array.getLength(oValues);
            if (valueLength <= 0) {
                return (M) this;
            }
            getThreadSqlCondition().getConditionSql().append(link).append(column.sqlColumn()).append(whether ? IN : NOT_IN).append(BRACKETS_LEFT);
            for (int i = 0; i < valueLength; i++) {
                if (0 == i) {
                    getThreadSqlCondition().getConditionSql().append(PARAM);
                } else {
                    getThreadSqlCondition().getConditionSql().append(COMMA).append(PARAM);
                }
                Object value = Array.get(oValues, i);
                if (value instanceof Enum) {
                    value = ((Enum<?>) value).name();
                }
                getThreadSqlCondition().getParamList().add(value);
            }
        } else if (oValues instanceof Iterable) {
            Iterator<?> iterator = ((Iterable<?>) oValues).iterator();
            if (!iterator.hasNext()) {
                return (M) this;
            }
            getThreadSqlCondition().getConditionSql().append(link).append(column.sqlColumn()).append(whether ? IN : NOT_IN).append(BRACKETS_LEFT);
            for (int i = 0; iterator.hasNext(); i++) {
                if (0 == i) {
                    getThreadSqlCondition().getConditionSql().append(PARAM);
                } else {
                    getThreadSqlCondition().getConditionSql().append(COMMA).append(PARAM);
                }
                getThreadSqlCondition().getParamList().add(iterator.next());
            }
        } else if (oValues instanceof CharSequence) {
            String values = oValues.toString();
            if (values.isEmpty()) {
                return (M) this;
            }
            getThreadSqlCondition().getConditionSql().append(link).append(column.sqlColumn()).append(whether ? IN : NOT_IN).append(BRACKETS_LEFT);
            if (values.contains(COMMA)) {
                for (int i = 0; i < values.split(COMMA).length; i++) {
                    if (0 == i) {
                        getThreadSqlCondition().getConditionSql().append(PARAM);
                    } else {
                        getThreadSqlCondition().getConditionSql().append(COMMA).append(PARAM);
                    }
                    getThreadSqlCondition().getParamList().add(values.split(COMMA)[i]);
                }
            } else {
                getThreadSqlCondition().getConditionSql().append(PARAM);
                getThreadSqlCondition().getParamList().add(values);
            }
        } else {
            throw new RuntimeException("Param value's value is not allowed!");
        }
        getThreadSqlCondition().getConditionSql().append(BRACKETS_RIGHT);
        return (M) this;
    }

    private M compare(String link, String symbol, DbColumn column, Object oValue) {
        getThreadSqlCondition().getConditionSql().append(link).append(column.sqlColumn()).append(symbol).append(PARAM);
        getThreadSqlCondition().getParamList().add(oValue);
        return (M) this;
    }

    private M like(String and, boolean b, DbColumn name, String value) {
        return like(and, b, name, value, LikeType.LIKE);
    }

    private M like(String link, boolean whether, DbColumn name, Object oValue, LikeType likeType) {
        getThreadSqlCondition().getConditionSql().append(link).append(name.sqlColumn()).append(whether ? LIKE : NOT_LIKE).append(PARAM);
        if (LikeType.START_WITH.equals(likeType)) {
            getThreadSqlCondition().getParamList().add(oValue + PERCENT);
        } else if (LikeType.END_WITH.equals(likeType)) {
            getThreadSqlCondition().getParamList().add(PERCENT + oValue);
        } else {
            getThreadSqlCondition().getParamList().add(PERCENT + oValue + PERCENT);
        }
        return (M) this;
    }

    private M isNull(String link, DbColumn name, boolean isNull) {
        if (isNull) {
            getThreadSqlCondition().getConditionSql().append(link).append(name.sqlColumn()).append(IS_NULL);
        } else {
            getThreadSqlCondition().getConditionSql().append(link).append(name.sqlColumn()).append(IS_NOT_NULL);
        }
        return (M) this;
    }

    private M isEmpty(String link, DbColumn name, boolean isNull) {
        if (isNull) {
            getThreadSqlCondition().getConditionSql().append(link).append(name.sqlColumn()).append(EQUAL).append(SQL_EMPTY);
        } else {
            getThreadSqlCondition().getConditionSql().append(link).append(name.sqlColumn()).append(NOT_EQUAL).append(SQL_EMPTY);
        }
        return (M) this;
    }

    public M groupBy(DbColumn name) {
        return groupBy(name, null);
    }

    public M groupBy(DbColumn column, String value) {
        if (null != value) {
            return groupBy(column.sqlColumn() + SPACE + value);
        }
        return groupBy(column.sqlColumn());
    }

    public M groupBy(String groupBy) {
        String key = GROUP_BY;
        if (null != getThreadSqlCondition().getGroup() && !"".equals(getThreadSqlCondition().getGroup())) {
            key = COMMA;
        }
        getThreadSqlCondition().setGroup(getThreadSqlCondition().getGroup() + key + groupBy);
        return (M) this;
    }

    public M order(DbColumn name) {
        return order(name, OrderEnum.ASC);
    }

    public M order(DbColumn column, OrderEnum value) {
        if (null == getThreadSqlCondition().getOrder() || "".equals(getThreadSqlCondition().getOrder())) {
            getThreadSqlCondition().setOrder(ORDER_BY + column.name() + SPACE + value);
        } else {
            getThreadSqlCondition().setOrder(getThreadSqlCondition().getOrder() + COMMA + SPACE + column.name() + SPACE + value);
        }
        return (M) this;
    }

    public M order(String orderStr, OrderEnum value) {
        if (null == getThreadSqlCondition().getOrder() || "".equals(getThreadSqlCondition().getOrder())) {
            getThreadSqlCondition().setOrder(ORDER_BY + orderStr + SPACE + value);
        } else {
            getThreadSqlCondition().setOrder(getThreadSqlCondition().getOrder() + COMMA + SPACE + orderStr + SPACE + value);
        }
        return (M) this;
    }

    public M limit(int from, int length) {
        if (from < 0 || length < 0) {
            throw new RuntimeException("Limit value is invalid!");
        }
        getThreadSqlCondition().setLimit(LIMIT + from + COMMA + length);
        return (M) this;
    }

    public M limit(int value) {
        if (value < 0) {
            throw new RuntimeException("Limit value is invalid!");
        }
        getThreadSqlCondition().setLimit(LIMIT + value);
        return (M) this;
    }

    public M and(M sqlCondition) {
        if (this == sqlCondition) {
            throw new RuntimeException("The and condition is error!");
        }
        return condition(AND, sqlCondition, false);
    }

    public M or(M sqlCondition) {
        if (this == sqlCondition) {
            throw new RuntimeException("The or condition is error!");
        }
        return condition(OR, sqlCondition, false);
    }

    public M andNot(M sqlCondition) {
        if (this == sqlCondition) {
            throw new RuntimeException("The and condition is error!");
        }
        return condition(AND, sqlCondition, true);
    }

    public M orNot(M sqlCondition) {
        if (this == sqlCondition) {
            throw new RuntimeException("The or condition is error!");
        }
        return condition(OR, sqlCondition, true);
    }

    public M andExists(String sql) {
        getThreadSqlCondition().getConditionSql().append(AND).append(EXISTS)
                .append(BRACKETS_LEFT).append(sql).append(BRACKETS_RIGHT);
        return (M) this;
    }

    public M andNotExists(String sql) {
        getThreadSqlCondition().getConditionSql().append(AND).append(NOT_EXISTS)
                .append(BRACKETS_LEFT).append(sql).append(BRACKETS_RIGHT);
        return (M) this;
    }

    public M andEqualBitAND(DbColumn column, int equalValue) {
        return operator(AND, column, Operator.and, equalValue, String.valueOf(equalValue), Boolean.TRUE);
    }

    public M andEqualBitOR(DbColumn column, int equalValue) {
        return operator(AND, column, Operator.or, equalValue, String.valueOf(equalValue), Boolean.TRUE);
    }

    public M andEqualBitNOT(DbColumn column, int equalValue) {
        return operator(AND, column, Operator.not, equalValue, String.valueOf(equalValue), Boolean.TRUE);
    }

    public M andEqualBitXOR(DbColumn column, int equalValue) {
        return operator(AND, column, Operator.xor, equalValue, String.valueOf(equalValue), Boolean.TRUE);
    }

    public M orEqualBitAND(DbColumn column, int equalValue) {
        return operator(OR, column, Operator.and, equalValue, String.valueOf(equalValue), Boolean.TRUE);
    }

    public M orEqualBitOR(DbColumn column, int equalValue) {
        return operator(OR, column, Operator.or, equalValue, String.valueOf(equalValue), Boolean.TRUE);
    }

    public M orEqualBitNOT(DbColumn column, int equalValue) {
        return operator(OR, column, Operator.not, equalValue, String.valueOf(equalValue), Boolean.TRUE);
    }

    public M orEqualBitXOR(DbColumn column, int equalValue) {
        return operator(OR, column, Operator.xor, equalValue, String.valueOf(equalValue), Boolean.TRUE);
    }

    public M andEqualBitAND(DbColumn column, int bitValue, int equalValue) {
        return operator(AND, column, Operator.and, bitValue, String.valueOf(equalValue), Boolean.TRUE);
    }

    public M andEqualBitOR(DbColumn column, int bitValue, int equalValue) {
        return operator(AND, column, Operator.or, bitValue, String.valueOf(equalValue), Boolean.TRUE);
    }

    public M andEqualBitNOT(DbColumn column, int bitValue, int equalValue) {
        return operator(AND, column, Operator.not, bitValue, String.valueOf(equalValue), Boolean.TRUE);
    }

    public M andEqualBitXOR(DbColumn column, int bitValue, int equalValue) {
        return operator(AND, column, Operator.xor, bitValue, String.valueOf(equalValue), Boolean.TRUE);
    }

    public M orEqualBitAND(DbColumn column, int bitValue, int equalValue) {
        return operator(OR, column, Operator.and, bitValue, String.valueOf(equalValue), Boolean.TRUE);
    }

    public M orEqualBitOR(DbColumn column, int bitValue, int equalValue) {
        return operator(OR, column, Operator.or, bitValue, String.valueOf(equalValue), Boolean.TRUE);
    }

    public M orEqualBitNOT(DbColumn column, int bitValue, int equalValue) {
        return operator(OR, column, Operator.not, bitValue, String.valueOf(equalValue), Boolean.TRUE);
    }

    public M orEqualBitXOR(DbColumn column, int bitValue, int equalValue) {
        return operator(OR, column, Operator.xor, bitValue, String.valueOf(equalValue), Boolean.TRUE);
    }

    public M andEqualBitAND(DbColumn column, int bitValue, DbColumn equalValue) {
        return operator(AND, column, Operator.and, bitValue, equalValue.name(), Boolean.TRUE);
    }

    public M andEqualBitOR(DbColumn column, int bitValue, DbColumn equalValue) {
        return operator(AND, column, Operator.or, bitValue, equalValue.name(), Boolean.TRUE);
    }

    public M andEqualBitNOT(DbColumn column, int bitValue, DbColumn equalValue) {
        return operator(AND, column, Operator.not, bitValue, equalValue.name(), Boolean.TRUE);
    }

    public M andEqualBitXOR(DbColumn column, int bitValue, DbColumn equalValue) {
        return operator(AND, column, Operator.xor, bitValue, equalValue.name(), Boolean.TRUE);
    }

    public M orEqualBitAND(DbColumn column, int bitValue, DbColumn equalValue) {
        return operator(OR, column, Operator.and, bitValue, equalValue.name(), Boolean.TRUE);
    }

    public M orEqualBitOR(DbColumn column, int bitValue, DbColumn equalValue) {
        return operator(OR, column, Operator.or, bitValue, equalValue.name(), Boolean.TRUE);
    }

    public M orEqualBitNOT(DbColumn column, int bitValue, DbColumn equalValue) {
        return operator(OR, column, Operator.not, bitValue, equalValue.name(), Boolean.TRUE);
    }

    public M orEqualBitXOR(DbColumn column, int bitValue, DbColumn equalValue) {
        return operator(OR, column, Operator.xor, bitValue, equalValue.name(), Boolean.TRUE);
    }

    public M andNotEqualBitAND(DbColumn column, int equalValue) {
        return operator(AND, column, Operator.and, equalValue, String.valueOf(equalValue), Boolean.FALSE);
    }

    public M andNotEqualBitOR(DbColumn column, int equalValue) {
        return operator(AND, column, Operator.or, equalValue, String.valueOf(equalValue), Boolean.FALSE);
    }

    public M andNotEqualBitNOT(DbColumn column, int equalValue) {
        return operator(AND, column, Operator.not, equalValue, String.valueOf(equalValue), Boolean.FALSE);
    }

    public M andNotEqualBitXOR(DbColumn column, int equalValue) {
        return operator(AND, column, Operator.xor, equalValue, String.valueOf(equalValue), Boolean.FALSE);
    }

    public M orNotEqualBitAND(DbColumn column, int equalValue) {
        return operator(OR, column, Operator.and, equalValue, String.valueOf(equalValue), Boolean.FALSE);
    }

    public M orNotEqualBitOR(DbColumn column, int equalValue) {
        return operator(OR, column, Operator.or, equalValue, String.valueOf(equalValue), Boolean.FALSE);
    }

    public M orNotEqualBitNOT(DbColumn column, int equalValue) {
        return operator(OR, column, Operator.not, equalValue, String.valueOf(equalValue), Boolean.FALSE);
    }

    public M orNotEqualBitXOR(DbColumn column, int equalValue) {
        return operator(OR, column, Operator.xor, equalValue, String.valueOf(equalValue), Boolean.FALSE);
    }

    public M andNotEqualBitAND(DbColumn column, int bitValue, int equalValue) {
        return operator(AND, column, Operator.and, bitValue, String.valueOf(equalValue), Boolean.FALSE);
    }

    public M andNotEqualBitOR(DbColumn column, int bitValue, int equalValue) {
        return operator(AND, column, Operator.or, bitValue, String.valueOf(equalValue), Boolean.FALSE);
    }

    public M andNotEqualBitNOT(DbColumn column, int bitValue, int equalValue) {
        return operator(AND, column, Operator.not, bitValue, String.valueOf(equalValue), Boolean.FALSE);
    }

    public M andNotEqualBitXOR(DbColumn column, int bitValue, int equalValue) {
        return operator(AND, column, Operator.xor, bitValue, String.valueOf(equalValue), Boolean.FALSE);
    }

    public M orNotEqualBitAND(DbColumn column, int bitValue, int equalValue) {
        return operator(OR, column, Operator.and, bitValue, String.valueOf(equalValue), Boolean.FALSE);
    }

    public M orNotEqualBitOR(DbColumn column, int bitValue, int equalValue) {
        return operator(OR, column, Operator.or, bitValue, String.valueOf(equalValue), Boolean.FALSE);
    }

    public M orNotEqualBitNOT(DbColumn column, int bitValue, int equalValue) {
        return operator(OR, column, Operator.not, bitValue, String.valueOf(equalValue), Boolean.FALSE);
    }

    public M orNotEqualBitXOR(DbColumn column, int bitValue, int equalValue) {
        return operator(OR, column, Operator.xor, bitValue, String.valueOf(equalValue), Boolean.FALSE);
    }

    public M andNotEqualBitAND(DbColumn column, int bitValue, DbColumn equalValue) {
        return operator(AND, column, Operator.and, bitValue, equalValue.name(), Boolean.FALSE);
    }

    public M andNotEqualBitOR(DbColumn column, int bitValue, DbColumn equalValue) {
        return operator(AND, column, Operator.or, bitValue, equalValue.name(), Boolean.FALSE);
    }

    public M andNotEqualBitNOT(DbColumn column, int bitValue, DbColumn equalValue) {
        return operator(AND, column, Operator.not, bitValue, equalValue.name(), Boolean.FALSE);
    }

    public M andNotEqualBitXOR(DbColumn column, int bitValue, DbColumn equalValue) {
        return operator(AND, column, Operator.xor, bitValue, equalValue.name(), Boolean.FALSE);
    }

    public M orNotEqualBitAND(DbColumn column, int bitValue, DbColumn equalValue) {
        return operator(OR, column, Operator.and, bitValue, equalValue.name(), Boolean.FALSE);
    }

    public M orNotEqualBitOR(DbColumn column, int bitValue, DbColumn equalValue) {
        return operator(OR, column, Operator.or, bitValue, equalValue.name(), Boolean.FALSE);
    }

    public M orNotEqualBitNOT(DbColumn column, int bitValue, DbColumn equalValue) {
        return operator(OR, column, Operator.not, bitValue, equalValue.name(), Boolean.FALSE);
    }

    public M orNotEqualBitXOR(DbColumn column, int bitValue, DbColumn equalValue) {
        return operator(OR, column, Operator.xor, bitValue, equalValue.name(), Boolean.FALSE);
    }

    private M operator(String link, DbColumn column, Operator operator, int bitValue, String equalValue, boolean equal) {
        getThreadSqlCondition().getConditionSql().append(link).append(column.sqlColumn()).append(operator.value).append(bitValue).append(equal ? EQUAL : NOT_EQUAL).append(equalValue);
        return (M) this;
    }

    public M andEqual(DbColumn name, String value) {
        return equals(AND, Boolean.TRUE, name, value);
    }

    public M andEqual(DbColumn name, Number value) {
        return equals(AND, Boolean.TRUE, name, value);
    }

    public M andEqual(DbColumn name, Date value) {
        return equals(AND, Boolean.TRUE, name, value);
    }

    public M andEqual(DbColumn name, Boolean value) {
        return equals(AND, Boolean.TRUE, name, value);
    }

    public M andEqual(DbColumn name1, DbColumn name2) {
        return equals(AND, Boolean.TRUE, name1, name2);
    }

    public M andNotEqual(DbColumn name, String value) {
        return equals(AND, Boolean.FALSE, name, value);
    }

    public M andNotEqual(DbColumn name, Number value) {
        return equals(AND, Boolean.FALSE, name, value);
    }

    public M andNotEqual(DbColumn name, Date value) {
        return equals(AND, Boolean.FALSE, name, value);
    }

    public M andNotEqual(DbColumn name, Boolean value) {
        return equals(AND, Boolean.FALSE, name, value);
    }

    public M andNotEqual(DbColumn name1, DbColumn name2) {
        return equals(AND, Boolean.FALSE, name1, name2);
    }

    public M orEqual(DbColumn name, String value) {
        return equals(OR, Boolean.TRUE, name, value);
    }

    public M orEqual(DbColumn name, Number value) {
        return equals(OR, Boolean.TRUE, name, value);
    }

    public M orEqual(DbColumn name, Date value) {
        return equals(OR, Boolean.TRUE, name, value);
    }

    public M orEqual(DbColumn name, Boolean value) {
        return equals(OR, Boolean.TRUE, name, value);
    }

    public M orEqual(DbColumn name1, DbColumn name2) {
        return equals(OR, Boolean.TRUE, name1, name2);
    }

    public M orNotEqual(DbColumn name, String value) {
        return equals(OR, Boolean.FALSE, name, value);
    }

    public M orNotEqual(DbColumn name, Number value) {
        return equals(OR, Boolean.FALSE, name, value);
    }

    public M orNotEqual(DbColumn name, Date value) {
        return equals(OR, Boolean.FALSE, name, value);
    }

    public M orNotEqual(DbColumn name, Boolean value) {
        return equals(OR, Boolean.FALSE, name, value);
    }

    public M orNotEqual(DbColumn name1, DbColumn name2) {
        return equals(OR, Boolean.FALSE, name1, name2);
    }

    public M andIn(DbColumn name, Iterable<?> value) {
        return in(AND, Boolean.TRUE, name, value);
    }

    public M andIn(DbColumn name, String... value) {
        return in(AND, Boolean.TRUE, name, value);
    }

    public M andIn(DbColumn name, Number... value) {
        return in(AND, Boolean.TRUE, name, value);
    }

    public M andIn(DbColumn name, Enum<?>... value) {
        return in(AND, Boolean.TRUE, name, value);
    }

    public M andIn(DbColumn name, CharSequence value) {
        return in(AND, Boolean.TRUE, name, value);
    }

    public M andNotIn(DbColumn name, Iterable<?> value) {
        return in(AND, Boolean.FALSE, name, value);
    }

    public M andNotIn(DbColumn name, String... value) {
        return in(AND, Boolean.FALSE, name, value);
    }

    public M andNotIn(DbColumn name, Number... value) {
        return in(AND, Boolean.FALSE, name, value);
    }

    public M andNotIn(DbColumn name, Enum<?>... value) {
        return in(AND, Boolean.FALSE, name, value);
    }

    public M andNotIn(DbColumn name, CharSequence value) {
        return in(AND, Boolean.FALSE, name, value);
    }

    public M orIn(DbColumn name, Iterable<?> value) {
        return in(OR, Boolean.TRUE, name, value);
    }

    public M orIn(DbColumn name, String... value) {
        return in(OR, Boolean.TRUE, name, value);
    }

    public M orIn(DbColumn name, Number... value) {
        return in(OR, Boolean.TRUE, name, value);
    }

    public M orIn(DbColumn name, Enum<?>... value) {
        return in(OR, Boolean.TRUE, name, value);
    }

    public M orIn(DbColumn name, CharSequence value) {
        return in(OR, Boolean.TRUE, name, value);
    }

    public M orNotIn(DbColumn name, Iterable<?> value) {
        return in(OR, Boolean.FALSE, name, value);
    }

    public M orNotIn(DbColumn name, String... value) {
        return in(OR, Boolean.FALSE, name, value);
    }

    public M orNotIn(DbColumn name, Number... value) {
        return in(OR, Boolean.FALSE, name, value);
    }

    public M orNotIn(DbColumn name, Enum<?>... value) {
        return in(OR, Boolean.FALSE, name, value);
    }

    public M orNotIn(DbColumn name, CharSequence value) {
        return in(OR, Boolean.FALSE, name, value);
    }

    public M andAfter(DbColumn name, Date value) {
        return compare(AND, GREATER, name, value);
    }

    public M andAfter(DbColumn name, String value) {
        return compare(AND, GREATER, name, value);
    }

    public M andAfter(DbColumn name, Number value) {
        return compare(AND, GREATER, name, value);
    }

    public M andAfterEqual(DbColumn name, Date value) {
        return compare(AND, GREATER_EQUAL, name, value);
    }

    public M andAfterEqual(DbColumn name, String value) {
        return compare(AND, GREATER_EQUAL, name, value);
    }

    public M andAfterEqual(DbColumn name, Number value) {
        return compare(AND, GREATER_EQUAL, name, value);
    }

    public M andBefore(DbColumn name, Date value) {
        return compare(AND, LESSER, name, value);
    }

    public M andBefore(DbColumn name, Number value) {
        return compare(AND, LESSER, name, value);
    }

    public M andBefore(DbColumn name, String value) {
        return compare(AND, LESSER, name, value);
    }

    public M andBeforeEqual(DbColumn name, Date value) {
        return compare(AND, LESSER_EQUAL, name, value);
    }

    public M andBeforeEqual(DbColumn name, Number value) {
        return compare(AND, LESSER_EQUAL, name, value);
    }

    public M andBeforeEqual(DbColumn name, String value) {
        return compare(AND, LESSER_EQUAL, name, value);
    }

    public M orAfter(DbColumn name, Date value) {
        return compare(OR, GREATER, name, value);
    }

    public M orAfter(DbColumn name, String value) {
        return compare(OR, GREATER, name, value);
    }

    public M orAfter(DbColumn name, Number value) {
        return compare(OR, GREATER, name, value);
    }

    public M orAfterEqual(DbColumn name, Date value) {
        return compare(OR, GREATER_EQUAL, name, value);
    }

    public M orAfterEqual(DbColumn name, String value) {
        return compare(OR, GREATER_EQUAL, name, value);
    }

    public M orAfterEqual(DbColumn name, Number value) {
        return compare(OR, GREATER_EQUAL, name, value);
    }

    public M orBefore(DbColumn name, Date value) {
        return compare(OR, LESSER, name, value);
    }

    public M orBefore(DbColumn name, Number value) {
        return compare(OR, LESSER, name, value);
    }

    public M orBefore(DbColumn name, String value) {
        return compare(OR, LESSER, name, value);
    }

    public M orBeforeEqual(DbColumn name, Date value) {
        return compare(OR, LESSER_EQUAL, name, value);
    }

    public M orBeforeEqual(DbColumn name, Number value) {
        return compare(OR, LESSER_EQUAL, name, value);
    }

    public M orBeforeEqual(DbColumn name, String value) {
        return compare(OR, LESSER_EQUAL, name, value);
    }

    public M andLike(DbColumn name, String value) {
        return like(AND, Boolean.TRUE, name, value);
    }

    public M andBeginWith(DbColumn name, String value) {
        return like(AND, Boolean.TRUE, name, value, LikeType.START_WITH);
    }

    public M andEndWith(DbColumn name, String value) {
        return like(AND, Boolean.TRUE, name, value, LikeType.END_WITH);
    }

    public M andNotLike(DbColumn name, String value) {
        return like(AND, Boolean.FALSE, name, value);
    }

    public M andNotBeginWith(DbColumn name, String value) {
        return like(AND, Boolean.FALSE, name, value, LikeType.START_WITH);
    }

    public M andNotEndWith(DbColumn name, String value) {
        return like(AND, Boolean.FALSE, name, value, LikeType.END_WITH);
    }

    public M orLike(DbColumn name, String value) {
        return like(OR, Boolean.TRUE, name, value);
    }

    public M orBeginWith(DbColumn name, String value) {
        return like(OR, Boolean.TRUE, name, value, LikeType.START_WITH);
    }

    public M orEndWith(DbColumn name, String value) {
        return like(OR, Boolean.TRUE, name, value, LikeType.END_WITH);
    }

    public M orNotLike(DbColumn name, String value) {
        return like(OR, Boolean.FALSE, name, value);
    }

    public M orNotBeginWith(DbColumn name, String value) {
        return like(OR, Boolean.FALSE, name, value, LikeType.START_WITH);
    }

    public M orNotEndWith(DbColumn name, String value) {
        return like(OR, Boolean.FALSE, name, value, LikeType.END_WITH);
    }

    public M andNull(DbColumn name) {
        return isNull(AND, name, Boolean.TRUE);
    }

    public M andNotNull(DbColumn name) {
        return isNull(AND, name, Boolean.FALSE);
    }

    public M orNull(DbColumn name) {
        return isNull(OR, name, Boolean.TRUE);
    }

    public M orNotNull(DbColumn name) {
        return isNull(OR, name, Boolean.FALSE);
    }

    public M andEmpty(DbColumn name) {
        return isEmpty(AND, name, Boolean.TRUE);
    }

    public M andNotEmpty(DbColumn name) {
        return isEmpty(AND, name, Boolean.FALSE);
    }

    public M orEmpty(DbColumn name) {
        return isEmpty(OR, name, Boolean.TRUE);
    }

    public M orNotEmpty(DbColumn name) {
        return isEmpty(OR, name, Boolean.FALSE);
    }

    public M inSet(String link, DbColumn column, Object oValues) {
        if (null == oValues) {
            return (M) this;
        }
        List<Object> valueList = new ArrayList<>();
        if (oValues.getClass().isArray()) {
            int valueLength = Array.getLength(oValues);
            if (valueLength <= 0) {
                return (M) this;
            }
            for (int i = 0; i < valueLength; i++) {
                valueList.add(Array.get(oValues, i));
            }
        } else if (oValues instanceof Iterable) {
            for (Object object : (Iterable<?>) oValues) {
                valueList.add(object);
            }
        } else if (oValues instanceof CharSequence) {
            String values = oValues.toString();
            if (values.isEmpty()) {
                return (M) this;
            }
            if (values.contains(COMMA)) {
                Collections.addAll(valueList, values.split(COMMA));
            } else {
                valueList.add(values);
            }
        } else {
            throw new RuntimeException("Param value's value is not allowed!");
        }
        if (!valueList.isEmpty()) {
            getThreadSqlCondition().getConditionSql().append(link).append(BRACKETS_LEFT);
            for (int i = 0; i < valueList.size(); i++) {
                if (0 != i) {
                    getThreadSqlCondition().getConditionSql().append(OR);
                }
                getThreadSqlCondition().getConditionSql().append(FUNCTION_FIND_IN_SET).append(BRACKETS_LEFT.trim()).append(PARAM).append(COMMA).append(column.sqlColumn()).append(BRACKETS_RIGHT.trim());
                getThreadSqlCondition().getParamList().add(valueList.get(i));
            }
            getThreadSqlCondition().getConditionSql().append(BRACKETS_RIGHT);
        }
        return (M) this;
    }

    public M andInSet(DbColumn name, Iterable<?> value) {
        return inSet(AND, name, value);
    }

    public M andInSet(DbColumn name, String... value) {
        return inSet(AND, name, value);
    }

    public M andInSet(DbColumn name, Number... value) {
        return inSet(AND, name, value);
    }

    public M andInSet(DbColumn name, CharSequence value) {
        return inSet(AND, name, value);
    }

    public M orInSet(DbColumn name, Iterable<?> value) {
        return inSet(OR, name, value);
    }

    public M orInSet(DbColumn name, String... value) {
        return inSet(OR, name, value);
    }

    public M orInSet(DbColumn name, Number... value) {
        return inSet(OR, name, value);
    }

    public M orInSet(DbColumn name, CharSequence value) {
        return inSet(OR, name, value);
    }

    /**
     * @return this
     * @deprecated on version 1.9.0
     */
    @Deprecated
    public M andIsNull(DbColumn name, boolean isNull) {
        return isNull(AND, name, isNull);
    }

    /**
     * @return this
     * @deprecated on version 1.9.0
     */
    @Deprecated
    public M orIsNull(DbColumn name, boolean isNull) {
        return isNull(OR, name, isNull);
    }

    private void appendFromConditionSql(StringBuffer selectSql, String condition) {
        selectSql.append(FROM).append(getSqlTableName());
        if (StrKit.notBlank(condition)) {
            if (condition.indexOf(ORDER_BY) == 0 || condition.indexOf(GROUP_BY) == 0 || condition.indexOf(LIMIT) == 0) {
                selectSql.append(SPACE).append(condition);
            } else {
                selectSql.append(WHERE).append(SPACE).append(condition);
            }
        }
    }

    public <T> List<T> findColumn(DbColumn column) {
        return findColumn(Boolean.TRUE, column);
    }

    private List<Object> filterLogParams(List<Object> list) {
        if (null == list) {
            return null;
        }
        try {
            return list.parallelStream().map(obj -> {
                if (obj.getClass().isArray()) {
                    Object first = Array.get(obj, 0);
                    int length = Array.getLength(obj);
                    return first + "..." + length;
                } else if (obj instanceof Iterable) {
                    Iterator<?> iterator = ((Iterable<?>) obj).iterator();
                    int length = 0;
                    String first = "";
                    while (iterator.hasNext()) {
                        if (0 == length) {
                            first = iterator.next().toString();
                        } else {
                            iterator.next();
                        }
                        length++;
                    }
                    return first + "..." + length;
                }
                return obj;
            }).collect(Collectors.toList());
        } catch (Exception e) {
            logger.error("list size_{} filter for log error: {}", list.size(), e.getMessage(), e);
        }
        return list;
    }

    public <T> List<T> findColumn(boolean clearCondition, DbColumn column) {
        try {
            String condition = getThreadSqlCondition().toSql();
            List<Object> list = getThreadSqlCondition().getParamList();
            StringBuffer selectSql = new StringBuffer();
            selectSql.append(genSelectSql(column));

            appendFromConditionSql(selectSql, condition);
            boolean showLog = getThreadSqlCondition().isShowLog();
            if (showLog) {
                if (AbstractEnhancerConfig.isShowSql()) {
                    logger.info("AbstractEnhancerModel.findColumn sql: {} --> params: {}", selectSql, filterLogParams(list));
                }
                if (AbstractEnhancerConfig.isShowSqlTime()) {
                    Long beginNano = System.nanoTime();
                    try {
                        return getDbPro().query(selectSql.toString(), list.toArray());
                    } finally {
                        logger.info("sql:{} query use time {}ms", selectSql, TimeKit.useTimeMillisecond(beginNano));
                    }
                }
            }
            return getDbPro().query(selectSql.toString(), list.toArray());
        } finally {
            if (clearCondition) {
                clearCondition();
            }
        }
    }

    public List<M> find(DbColumn... columns) {
        return find(Boolean.TRUE, columns);
    }

    public List<M> find(boolean clearCondition, DbColumn... columns) {
        try {
            String condition = getThreadSqlCondition().toSql();
            List<Object> list = getThreadSqlCondition().getParamList();
            StringBuffer selectSql = new StringBuffer();
            selectSql.append(genSelectSql(columns));

            appendFromConditionSql(selectSql, condition);
            boolean showLog = getThreadSqlCondition().isShowLog();
            if (showLog) {
                if (AbstractEnhancerConfig.isShowSql()) {
                    logger.info("AbstractEnhancerModel.find sql: {} --> params: {}", selectSql, list);
                }
                if (AbstractEnhancerConfig.isShowSqlTime()) {
                    Long beginNano = System.nanoTime();
                    try {
                        return super.find(selectSql.toString(), list.toArray());
                    } finally {
                        logger.info("find sql:{} query use time {}ms", selectSql, TimeKit.useTimeMillisecond(beginNano));
                    }
                }
            }
            return super.find(selectSql.toString(), list.toArray());
        } finally {
            if (clearCondition) {
                clearCondition();
            }
        }
    }

    public M findFirst(boolean clearCondition, DbColumn... columns) {
        List<M> list = find(clearCondition, columns);
        if (null == list || list.isEmpty()) {
            return null;
        }
        return list.get(0);
    }

    public M findFirst(DbColumn... columns) {
        List<M> list = find(columns);
        if (null == list || list.isEmpty()) {
            return null;
        }
        return list.get(0);
    }

    public M find(DbColumn maxColumn, Boolean maxOrMin) {
        if (null == maxColumn || null == maxOrMin) {
            return null;
        }
        return null;
    }

    public M forUpdate(boolean find) {
        getThreadSqlCondition().setForUpdate(Boolean.TRUE);
        if (find) {
            return findFirst();
        }
        return (M) this;
    }

    public M forUpdate() {
        return forUpdate(Boolean.FALSE);
    }

    public Page<M> paginate(int pageNumber, int pageSize, boolean clearCondition, DbColumn... columns) {
        try {
            String condition = getThreadSqlCondition().toSql();
            List<Object> list = getThreadSqlCondition().getParamList();

            StringBuffer selectSql = new StringBuffer();
            appendFromConditionSql(selectSql, condition);
            boolean showLog = getThreadSqlCondition().isShowLog();
            if (showLog) {
                if (AbstractEnhancerConfig.isShowSql()) {
                    logger.info("AbstractEnhancerModel.paginate sql: {} --> params: {}", selectSql.toString(), list);
                }
                if (AbstractEnhancerConfig.isShowSqlTime()) {
                    Long beginNano = System.nanoTime();
                    try {
                        return super.paginate(pageNumber, pageSize, genSelectSql(columns), selectSql.toString(), list.toArray());
                    } finally {
                        logger.info("sql:{} paginate({}, {}) use time {}ms", selectSql.toString(), pageNumber, pageSize, TimeKit.useTimeMillisecond(beginNano));
                    }
                }
            }
            return super.paginate(pageNumber, pageSize, genSelectSql(columns), selectSql.toString(), list.toArray());
        } finally {
            if (clearCondition) {
                clearCondition();
            }
        }
    }

    public Page<M> paginate(int pageNumber, int pageSize, DbColumn... columns) {
        return paginate(pageNumber, pageSize, Boolean.TRUE, columns);
    }

    /**
     * 生成要查询的字段 sql
     *
     * @param columns 字段列表
     */
    private String genSelectSql(DbColumn... columns) {
        StringBuilder selectSql = new StringBuilder();
        selectSql.append(SELECT);
        if (null != columns && columns.length > 0) {
            for (int i = 0; i < columns.length; i++) {
                DbColumn column = columns[i];
                if (0 == i) {
                    selectSql.append(column.sqlColumn());
                } else {
                    selectSql.append(COMMA).append(column.sqlColumn());
                }
            }
        } else {
            selectSql.append(ALL);
        }
        return selectSql.toString();
    }

    public DbPro getDbPro() {
        String configName = getConfigName();
        if (null == configName || configName.isEmpty()) {
            return Db.use();
        }
        return Db.use(configName);
    }

    @Override
    public boolean delete() {
        return delete(Boolean.TRUE);
    }

    public boolean delete(boolean clearCondition) {
        String conditionSql = getThreadSqlCondition().toSql();
        if (null == conditionSql || conditionSql.trim().isEmpty()) {
            return super.delete();
        }

        int delCount = deleteCount(clearCondition);
        return delCount > 0;
    }

    public int deleteCount() {
        return deleteCount(Boolean.TRUE);
    }

    public int deleteCount(boolean clearCondition) {
        try {
            String conditionSql = getThreadSqlCondition().toSql();
            if (null == conditionSql || conditionSql.trim().isEmpty()) {
                return 0;
            }
            String deleteSql = DELETE + FROM + getSqlTableName() + WHERE + conditionSql;
            boolean showLog = getThreadSqlCondition().isShowLog();
            if (showLog) {
                if (AbstractEnhancerConfig.isShowSql()) {
                    logger.info("AbstractEnhancerModel.delete sql: {} --> params: {}", deleteSql, getThreadSqlCondition().getParamList());
                }
                if (AbstractEnhancerConfig.isShowSqlTime()) {
                    Long beginNano = System.nanoTime();
                    try {
                        return getDbPro().update(deleteSql, getThreadSqlCondition().getParamList().toArray());
                    } finally {
                        logger.info("sql:{} delete use time {}ms", deleteSql, TimeKit.useTimeMillisecond(beginNano));
                    }
                }
            }
            return getDbPro().update(deleteSql, getThreadSqlCondition().getParamList().toArray());
        } finally {
            if (clearCondition) {
                clearCondition();
            }
        }
    }

    public long count() {
        return count(Boolean.TRUE);
    }

    public long count(boolean clearCondition) {
        try {
            StringBuilder countSql = new StringBuilder();
            countSql.append(SELECT).append(DBFunction.count(1)).append(FROM).append(getSqlTableName());
            String condition = getThreadSqlCondition().toSql();
            if (null != condition && !condition.trim().isEmpty()) {
                countSql.append(WHERE).append(SPACE).append(condition);
            }
            boolean showLog = getThreadSqlCondition().isShowLog();
            if (showLog) {
                if (AbstractEnhancerConfig.isShowSql()) {
                    logger.info("AbstractEnhancerModel.count sql: {} --> params: {}", countSql.toString(), getThreadSqlCondition().getParamList());
                }
                if (AbstractEnhancerConfig.isShowSqlTime()) {
                    Long beginNano = System.nanoTime();
                    try {
                        return getDbPro().queryLong(countSql.toString(), getThreadSqlCondition().getParamList().toArray());
                    } finally {
                        logger.info("sql:{} query count use time {}ms", countSql.toString(), TimeKit.useTimeMillisecond(beginNano));
                    }
                }
            }
            return getDbPro().queryLong(countSql.toString(), getThreadSqlCondition().getParamList().toArray());
        } finally {
            if (clearCondition) {
                clearCondition();
            }
        }
    }

    public BigDecimal sum(DbColumn column) {
        return sum(column, Boolean.TRUE);
    }

    public BigDecimal sum(DbColumn column, boolean clearCondition) {
        try {
            StringBuilder countSql = new StringBuilder();
            countSql.append(SELECT).append(DBFunction.sum(column)).append(FROM).append(getSqlTableName());
            String condition = getThreadSqlCondition().toSql();
            if (null != condition && !condition.trim().isEmpty()) {
                countSql.append(WHERE).append(SPACE).append(condition);
            }
            boolean showLog = getThreadSqlCondition().isShowLog();
            if (showLog) {
                if (AbstractEnhancerConfig.isShowSql()) {
                    logger.info("AbstractEnhancerModel.sum sql: {} --> params: {}", countSql.toString(), getThreadSqlCondition().getParamList());
                }
                if (AbstractEnhancerConfig.isShowSqlTime()) {
                    Long beginNano = System.nanoTime();
                    try {
                        return getDbPro().queryBigDecimal(countSql.toString(), getThreadSqlCondition().getParamList().toArray());
                    } finally {
                        logger.info("sql:{} query sum use time {}ms", countSql.toString(), TimeKit.useTimeMillisecond(beginNano));
                    }
                }
            }
            return getDbPro().queryBigDecimal(countSql.toString(), getThreadSqlCondition().getParamList().toArray());
        } finally {
            if (clearCondition) {
                clearCondition();
            }
        }
    }

    /**
     * 含有表达式的修改
     * eg. id = id + 1
     *
     * @param column     字段
     * @param expression 表达式
     */
    public M updateWithExpression(DbColumn column, String expression) {
        if (null == expression || expression.isEmpty()) {
            return (M) this;
        }
        if (null == getThreadSqlCondition().getUpdateSetSql()) {
            getThreadSqlCondition().setUpdateSetSql(new StringBuilder());
            getThreadSqlCondition().setUpdateParamList(new ArrayList<>());
        } else {
            getThreadSqlCondition().getUpdateSetSql().append(COMMA);
        }
        getThreadSqlCondition().getUpdateSetSql().append(column.sqlColumn()).append(EQUAL).append(expression);
        return (M) this;
    }

    public M columnAdd(DbColumn dbColumn, Number value) {
        return updateWithOperator(dbColumn, Operator.ADD, value);
    }

    /**
     * 字段值自增
     */
    public M columnAdd(DbColumn dbColumn, String value) {
        return updateWithOperator(dbColumn, Operator.ADD, value);
    }

    /**
     * 字段值自减
     */
    public M columnSubtract(DbColumn dbColumn, Number value) {
        return updateWithOperator(dbColumn, Operator.SUBTRACT, value);
    }

    /**
     * 字段值自乘
     */
    public M columnMultiply(DbColumn dbColumn, Number value) {
        return updateWithOperator(dbColumn, Operator.MULTIPLY, value);
    }

    /**
     * 字段值自除
     */
    public M columnDivide(DbColumn dbColumn, Number value) {
        return updateWithOperator(dbColumn, Operator.DIVIDE, value);
    }

    public M updateWithOperator(DbColumn updateColumn, Operator operator, Object value) {
        return updateWithOperator(updateColumn, updateColumn, operator, value);
    }

    public M updateWithOperator(DbColumn updateColumn, DbColumn fromColumn, Operator operator, Object value) {
        fromColumn = null == fromColumn ? updateColumn : fromColumn;
        if (null == getThreadSqlCondition().getUpdateSetSql()) {
            getThreadSqlCondition().setUpdateSetSql(new StringBuilder());
            getThreadSqlCondition().setUpdateParamList(new ArrayList<>());
        } else {
            getThreadSqlCondition().getUpdateSetSql().append(COMMA);
        }
        if (value instanceof Number) {
            getThreadSqlCondition().getUpdateSetSql().append(updateColumn.sqlColumn()).append(EQUAL).append(fromColumn.sqlColumn()).append(SPACE).append(operator.value).append(PARAM);
            getThreadSqlCondition().getUpdateParamList().add(value);
        } else if (value instanceof CharSequence && Operator.ADD.equals(operator)) {
            getThreadSqlCondition().getUpdateSetSql().append(updateColumn.sqlColumn()).append(EQUAL).append(DBFunction.ifNull(fromColumn, PARAM, DBFunction.concat(fromColumn, PARAM)));
            getThreadSqlCondition().getUpdateParamList().add(value);
            getThreadSqlCondition().getUpdateParamList().add(value);
        }
        return (M) this;
    }

    @Override
    public boolean update() {
        return this.update(Boolean.TRUE);
    }

    public synchronized boolean update(boolean clearCondition) {
        String conditionSql = getThreadSqlCondition().toSql();
        if (null == conditionSql || conditionSql.trim().isEmpty()) {
            return super.update();
        }
        int upCount = updateCount(clearCondition);
        return upCount > 0;
    }

    public int updateCount() {
        return updateCount(Boolean.TRUE);
    }

    public synchronized int updateCount(boolean clearCondition) {
        String conditionSql = getThreadSqlCondition().toSql();
        if (null == conditionSql || conditionSql.trim().isEmpty()) {
            return 0;
        }
        try {
            Set<String> modifyColumns = _getModifyFlag();
            StringBuilder sqlSB = null;
            List<Object> updateParams = new ArrayList<>();
            for (String strColumn : modifyColumns) {
                Object oValue = get(strColumn);
                if (containsColumn(strColumn)) {
                    if (null == sqlSB) {
                        sqlSB = new StringBuilder();
                        sqlSB.append(UPDATE).append(getSqlTableName()).append(SET).append(SqlKey.SQL_QUOTES).append(strColumn).append(SqlKey.SQL_QUOTES).append(EQUAL).append(PARAM);
                    } else {
                        sqlSB.append(COMMA).append(SqlKey.SQL_QUOTES).append(strColumn).append(SqlKey.SQL_QUOTES).append(EQUAL).append(PARAM);
                    }
                    updateParams.add(oValue);
                }
            }
            if (null == sqlSB && null == getThreadSqlCondition().getUpdateSetSql()) {
                return 0;
            } else if (null == sqlSB) {
                sqlSB = new StringBuilder();
                sqlSB.append(UPDATE).append(getSqlTableName()).append(SET).append(getThreadSqlCondition().getUpdateSetSql());
                updateParams.addAll(getThreadSqlCondition().getUpdateParamList());
            } else if (null != getThreadSqlCondition().getUpdateSetSql()) {
                sqlSB.append(COMMA).append(getThreadSqlCondition().getUpdateSetSql());
                updateParams.addAll(getThreadSqlCondition().getUpdateParamList());
            }
            sqlSB.append(WHERE).append(getThreadSqlCondition().toSql());
            updateParams.addAll(getThreadSqlCondition().getParamList());

            int upCount = 0;
            boolean showLog = getThreadSqlCondition().isShowLog();
            if (showLog) {
                if (AbstractEnhancerConfig.isShowSql()) {
                    logger.info("AbstractEnhancerModel.update sql: {} --> params: {}", sqlSB.toString(), updateParams);
                }
                if (AbstractEnhancerConfig.isShowSqlTime()) {
                    Long beginNano = System.nanoTime();
                    try {
                        upCount = getDbPro().update(sqlSB.toString(), updateParams.toArray());
                    } finally {
                        logger.info("sql:{} update use time {}ms", sqlSB.toString(), TimeKit.useTimeMillisecond(beginNano));
                    }
                } else {
                    upCount = getDbPro().update(sqlSB.toString(), updateParams.toArray());
                }
            } else {
                upCount = getDbPro().update(sqlSB.toString(), updateParams.toArray());
            }
            return upCount;
        } finally {
            if (clearCondition) {
                clearCondition();
            }
        }
    }

    private <H extends AbstractEnhancerModel<?>> String getHistoryMainId(H history) {
        String mainId = getTableName() + "_" + NORMAL_PRIMARY_ID.name();
        if (history.containsColumn(mainId)) {
            return mainId;
        }
        return null;
    }

    public <History extends AbstractEnhancerModel<History>> int toHistory(History history, DbColumn[] historyColumns) {
        String sqlCondition = toConditionSql();
        if (StringKit.isBlank(sqlCondition)) {
            throw new RuntimeException("delete_" + getSqlTableName() + " to history_" + history.getSqlTableName() + " must has condition!");
        }
        StringBuilder insertSql = new StringBuilder();
        StringBuilder selectSql = new StringBuilder();
        String mainId = getHistoryMainId(history);
        for (DbColumn historyColumn : historyColumns) {
            DbColumn myColumn = null;
            if (containsColumn(historyColumn.name())) {
                myColumn = historyColumn;
            } else if (null != mainId && historyColumn.name().equals(mainId) && containsColumn(NORMAL_PRIMARY_ID.name())) {
                myColumn = NORMAL_PRIMARY_ID;
            }
            if (null == myColumn) {
                continue;
            }
            if (selectSql.length() == 0) {
                selectSql.append(SELECT);
            } else {
                selectSql.append(COMMA);
            }
            selectSql.append(myColumn.sqlColumn());
            if (insertSql.length() == 0) {
                insertSql.append(INSERT).append(INTO).append(history.getSqlTableName()).append(BRACKETS_LEFT);
            } else {
                insertSql.append(COMMA);
            }
            insertSql.append(historyColumn.sqlColumn());
        }
        insertSql.append(BRACKETS_RIGHT);
        selectSql.append(FROM).append(getSqlTableName()).append(WHERE_1);
        insertSql.append(SPACE).append(selectSql).append(AND).append(sqlCondition);
        int toHistoryCount = 0;
        try {
            Object[] params = getParamList().toArray();
            logger.info("{} toHistory sql: {} params: {}", getFinalTableName(), insertSql, JsonKit.toJson(params));
            toHistoryCount = getDbPro().update(insertSql.toString(), params);
            this.delete();
        } finally {
            clearCondition();
        }
        return toHistoryCount;
    }

    public void clearCondition() {
        getThreadSqlCondition().clearCondition();
    }

    private synchronized SqlCondition getThreadSqlCondition() {
        if (null == threadLocal.get()) {
            threadLocal.set(new SqlCondition());
        }
        return threadLocal.get();
    }

    public int[] batchSave(List<M> recordList) {
        if (AbstractEnhancerConfig.isShowSqlTime()) {
            Long beginNano = System.nanoTime();
            try {
                return getDbPro().batchSave(recordList, recordList.size());
            } finally {
                logger.info("db table_{} batchSave[{}] records use time {}ms", getSqlTableName(), recordList.size(), TimeKit.useTimeMillisecond(beginNano));
            }
        }
        return getDbPro().batchSave(recordList, recordList.size());
    }

    public int[] batchUpdate(List<M> recordList) {
        if (AbstractEnhancerConfig.isShowSqlTime()) {
            Long beginNano = System.nanoTime();
            try {
                return getDbPro().batchUpdate(recordList, recordList.size());
            } finally {
                logger.info("db table_{} batchUpdate[{}] records use time {}ms", getSqlTableName(), recordList.size(), TimeKit.useTimeMillisecond(beginNano));
            }
        }
        return getDbPro().batchUpdate(recordList, recordList.size());
    }

    public List<M> pageList(int page, int pageSize, DbColumn... columns) {
        int startIndex = (page - 1) * pageSize;
        return limit(startIndex, pageSize).find(columns);
    }

    @Override
    public List<M> find(String sql) {
        if (AbstractEnhancerConfig.isShowSqlTime()) {
            Long beginNano = System.nanoTime();
            try {
                return super.find(sql);
            } finally {
                logger.info("sql:{} find use time {}ms", sql, TimeKit.useTimeMillisecond(beginNano));
            }
        }
        return super.find(sql);
    }

    @Override
    public Page<M> paginate(int pageNumber, int pageSize, String select, String sqlExceptSelect, Object... paras) {
        if (AbstractEnhancerConfig.isShowSqlTime()) {
            Long beginNano = System.nanoTime();
            try {
                return super.paginate(pageNumber, pageSize, select, sqlExceptSelect, paras);
            } finally {
                logger.info("sql:{} paginate({},{}) use time {}ms", select + sqlExceptSelect, pageNumber, pageSize, TimeKit.useTimeMillisecond(beginNano));
            }
        }
        return super.paginate(pageNumber, pageSize, select, sqlExceptSelect, paras);
    }

    @Override
    public Page<M> paginate(int pageNumber, int pageSize, String select, String sqlExceptSelect) {
        if (AbstractEnhancerConfig.isShowSqlTime()) {
            Long beginNano = System.nanoTime();
            try {
                return super.paginate(pageNumber, pageSize, select, sqlExceptSelect);
            } finally {
                logger.info("sql:{} paginate({},{}) use time {}ms", select + sqlExceptSelect, pageNumber, pageSize, TimeKit.useTimeMillisecond(beginNano));
            }
        }
        return super.paginate(pageNumber, pageSize, select, sqlExceptSelect);
    }

    @Override
    public Page<M> paginate(int pageNumber, int pageSize, boolean isGroupBySql, String select, String sqlExceptSelect, Object... paras) {
        if (AbstractEnhancerConfig.isShowSqlTime()) {
            Long beginNano = System.nanoTime();
            try {
                return super.paginate(pageNumber, pageSize, isGroupBySql, select, sqlExceptSelect, paras);
            } finally {
                logger.info("sql:{} paginate({},{}) isGroupBySql_{} use time {}ms", select + sqlExceptSelect, pageNumber, pageSize, isGroupBySql, TimeKit.useTimeMillisecond(beginNano));
            }
        }
        return super.paginate(pageNumber, pageSize, isGroupBySql, select, sqlExceptSelect, paras);
    }

    @Override
    public Page<M> paginateByFullSql(int pageNumber, int pageSize, String totalRowSql, String findSql, Object... paras) {
        if (AbstractEnhancerConfig.isShowSqlTime()) {
            Long beginNano = System.nanoTime();
            try {
                return super.paginateByFullSql(pageNumber, pageSize, totalRowSql, findSql, paras);
            } finally {
                logger.info("sql:{} paginateByFullSql({},{}) use time {}ms", findSql, pageNumber, pageSize, TimeKit.useTimeMillisecond(beginNano));
            }
        }
        return super.paginateByFullSql(pageNumber, pageSize, totalRowSql, findSql, paras);
    }

    @Override
    public Page<M> paginateByFullSql(int pageNumber, int pageSize, boolean isGroupBySql, String totalRowSql, String findSql, Object... paras) {
        if (AbstractEnhancerConfig.isShowSqlTime()) {
            Long beginNano = System.nanoTime();
            try {
                return super.paginateByFullSql(pageNumber, pageSize, isGroupBySql, totalRowSql, findSql, paras);
            } finally {
                logger.info("sql:{} paginateByFullSql({},{}) isGroupBySql_{} use time {}ms", findSql, pageNumber, pageSize, isGroupBySql, TimeKit.useTimeMillisecond(beginNano));
            }
        }
        return super.paginateByFullSql(pageNumber, pageSize, isGroupBySql, totalRowSql, findSql, paras);
    }

    @Override
    public boolean save() {
        if (AbstractEnhancerConfig.isShowSqlTime()) {
            Long beginNano = System.nanoTime();
            try {
                return super.save();
            } finally {
                logger.info(" db table_{} save use time {}ms", getSqlTableName(), TimeKit.useTimeMillisecond(beginNano));
            }
        }
        return super.save();
    }

    @Override
    public boolean deleteById(Object idValue) {
        if (AbstractEnhancerConfig.isShowSqlTime()) {
            Long beginNano = System.nanoTime();
            try {
                return super.deleteById(idValue);
            } finally {
                logger.info(" db table_{} deleteById use time {}ms", getSqlTableName(), TimeKit.useTimeMillisecond(beginNano));
            }
        }
        return super.deleteById(idValue);
    }

    public boolean deleteById(Object... idValues) {
        if (AbstractEnhancerConfig.isShowSqlTime()) {
            Long beginNano = System.nanoTime();
            try {
                return super.deleteById(idValues);
            } finally {
                logger.info(" db table_{} deleteById(...) time {}ms", getSqlTableName(), TimeKit.useTimeMillisecond(beginNano));
            }
        }
        return super.deleteById(idValues);
    }

    @Override
    public List<M> find(String sql, Object... paras) {
        if (AbstractEnhancerConfig.isShowSqlTime()) {
            Long beginNano = System.nanoTime();
            try {
                return super.find(sql, paras);
            } finally {
                logger.info(" db table_{} find(sql, ...) use time {}ms", getSqlTableName(), TimeKit.useTimeMillisecond(beginNano));
            }
        }
        return super.find(sql, paras);
    }

    @Override
    public M findFirst(String sql, Object... paras) {
        if (AbstractEnhancerConfig.isShowSqlTime()) {
            Long beginNano = System.nanoTime();
            try {
                return super.findFirst(sql, paras);
            } finally {
                logger.info(" db table_{} findFirst(sql, ...) use time {}ms", getSqlTableName(), TimeKit.useTimeMillisecond(beginNano));
            }
        }
        return super.findFirst(sql, paras);
    }

    @Override
    public M findFirst(String sql) {
        if (AbstractEnhancerConfig.isShowSqlTime()) {
            Long beginNano = System.nanoTime();
            try {
                return super.findFirst(sql);
            } finally {
                logger.info(" db table_{} findFirst(sql) use time {}ms", getSqlTableName(), TimeKit.useTimeMillisecond(beginNano));
            }
        }
        return super.findFirst(sql);
    }

    @Override
    public M findById(Object idValue) {
        if (AbstractEnhancerConfig.isShowSqlTime()) {
            Long beginNano = System.nanoTime();
            try {
                return super.findById(idValue);
            } finally {
                logger.info(" db table_{} findById use time {}ms", getSqlTableName(), TimeKit.useTimeMillisecond(beginNano));
            }
        }
        return super.findById(idValue);
    }

    @Override
    public M findByIds(Object... idValues) {
        if (AbstractEnhancerConfig.isShowSqlTime()) {
            Long beginNano = System.nanoTime();
            try {
                return super.findByIds(idValues);
            } finally {
                logger.info(" db table_{} findById(...) use time {}ms", getSqlTableName(), TimeKit.useTimeMillisecond(beginNano));
            }
        }
        return super.findByIds(idValues);
    }

    @Override
    public M findByIdLoadColumns(Object idValue, String columns) {
        if (AbstractEnhancerConfig.isShowSqlTime()) {
            Long beginNano = System.nanoTime();
            try {
                return super.findByIdLoadColumns(idValue, columns);
            } finally {
                logger.info(" db table_{} findByIdLoadColumns use time {}ms", getSqlTableName(), TimeKit.useTimeMillisecond(beginNano));
            }
        }
        return super.findByIdLoadColumns(idValue, columns);
    }

    @Override
    public M findByIdLoadColumns(Object[] idValues, String columns) {
        if (AbstractEnhancerConfig.isShowSqlTime()) {
            Long beginNano = System.nanoTime();
            try {
                return super.findByIdLoadColumns(idValues, columns);
            } finally {
                logger.info(" db table_{} findByIdLoadColumns([],[]) use time {}ms", getSqlTableName(), TimeKit.useTimeMillisecond(beginNano));
            }
        }
        return super.findByIdLoadColumns(idValues, columns);
    }

    @Override
    public List<M> find(SqlPara sqlPara) {
        if (AbstractEnhancerConfig.isShowSqlTime()) {
            Long beginNano = System.nanoTime();
            try {
                return super.find(sqlPara);
            } finally {
                logger.info(" db table_{} find(sqlPara) use time {}ms", getSqlTableName(), TimeKit.useTimeMillisecond(beginNano));
            }
        }
        return super.find(sqlPara);
    }

    @Override
    public M findFirst(SqlPara sqlPara) {
        if (AbstractEnhancerConfig.isShowSqlTime()) {
            Long beginNano = System.nanoTime();
            try {
                return super.findFirst(sqlPara);
            } finally {
                logger.info(" db table_{} findFirst(sqlPara) use time {}ms", getSqlTableName(), TimeKit.useTimeMillisecond(beginNano));
            }
        }
        return super.findFirst(sqlPara);
    }

    @Override
    public Page<M> paginate(int pageNumber, int pageSize, SqlPara sqlPara) {
        if (AbstractEnhancerConfig.isShowSqlTime()) {
            Long beginNano = System.nanoTime();
            try {
                return super.paginate(pageNumber, pageSize, sqlPara);
            } finally {
                logger.info(" db table_{} paginate(sqlPara) use time {}ms", getSqlTableName(), TimeKit.useTimeMillisecond(beginNano));
            }
        }
        return super.paginate(pageNumber, pageSize, sqlPara);
    }

    public int update(String sql) {
        boolean showLog = getThreadSqlCondition().isShowLog();
        if (showLog) {
            if (AbstractEnhancerConfig.isShowSql()) {
                logger.info("AbstractEnhancerModel.update sql: {}", sql);
            }
            if (AbstractEnhancerConfig.isShowSqlTime()) {
                Long beginNano = System.nanoTime();
                try {
                    return getDbPro().update(sql);
                } finally {
                    logger.info(" db table_{} update(sql) use time {}ms", getSqlTableName(), TimeKit.useTimeMillisecond(beginNano));
                }
            }
        }
        return getDbPro().update(sql);
    }
}
